/***************************************************************
*  Name:  asn1
*	    
*  Synopsis:  asn1 parser
*    	    
*  Description:
*     	    
*  History:
*      	         dietrich - Jan 18, 2012:  Created.
*
***************************************************************/

/* $Id: asn1.c,v 1.5 2012/01/20 21:07:45 pascal Exp pascal $ */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <gmp.h>
#include <time.h>

/* $Id: asn1.c,v 1.5 2012/01/20 21:07:45 pascal Exp pascal $ */

//#include "base64.h"
#include "asn1.h"


int asn1parse (const unsigned char  *buffer,	       int length,
	       struct asn1struct *top_level_token)
{
  unsigned int tag;
  unsigned char tag_length_byte;
  unsigned long tag_length;
  const unsigned char *ptr;
  const unsigned char *ptr_begin;
  struct asn1struct *token;


  ptr=buffer;
  token=top_level_token;

  while (length)
  {
    ptr_begin = ptr;
    
    // start is a tag.
    tag = *ptr++;
    length--;

    // High tag numbers (>31)
    if ((tag & 0x1F) == 0x1F)
    {
      tag = 0;
      while (*ptr & 0x80) {
	tag <<= 8;
	tag |= *ptr & 0x7F;
	ptr++;  // TODO should also test on length.
      }
    }

    tag_length_byte = *ptr++;
    length--;

    //  printf("TAG %x, tag_length_byte %x\n",tag,tag_length_byte);

    // Does not handle infinite length encodings.
    if (tag_length_byte & 0x80) {
      // The tag length byte contains the numbre of bytes in the length field.
      const unsigned char * len_ptr = ptr;
      tag_length = 0;
      while ( (len_ptr - ptr) < (tag_length_byte & 0x7F))
      {
	tag_length <<= 8;
	tag_length |= *(len_ptr++);
	length --;
      }
      ptr = len_ptr;
    }
    else
    {
      tag_length = tag_length_byte;
    }

    printf("TAG %x, tag_length %x\n",tag,tag_length);

    // high tag numbers.
    token -> tag = tag & 0x1f;
    token -> constructed = tag & 0x20;
    token -> tag_class = (tag & 0xC0) >> 6;
    token -> length = tag_length;
    token -> data = ptr;
    token -> children = NULL;
    token -> next = NULL;

    // constructed tag.
    if (tag & 0x20) {
      token -> length = tag_length + (ptr - ptr_begin);
      token -> data = ptr_begin;
      // append child and recurse.
      token->children = (struct asn1struct *)malloc(sizeof(struct asn1struct));
      asn1parse (ptr, tag_length, token->children);
    }

    ptr += tag_length;
    length -= tag_length;

    // point to the next tag in the buffer.
    if (length) {
      token->next=(struct asn1struct *)malloc(sizeof(struct asn1struct));
      token=token->next;
    }
  }
  
  return 0;
}


void print_oid(const unsigned char *data, int length)
{	
	int i;
	int f,s;
	const unsigned char * p = data;
	
	
	for ( i = 0; i < length; i++ )
	{
		printf( "%.02x ", data[ i ] );
	}
	
	/* print OID in decimalf form */
	/* first byte is Z=40x+y */
	f=*p/40;
	s=*p%40;
	printf(" /OID: %d.%d.",f,s);
	
	p++;
	s=length-1;
	
	while(s>0) {
		if (*p & 0x80) {
			unsigned long l=0;
			int  bcont=1;
			do {
				l<<=7;
				l|= *p & 0x7f;
				bcont = (*p & 0x80);
				p++;
				s--;
			} while ((s>0) && bcont);
			// now s==0 of bit7 is 0
			
			printf("%ld.",l);
		} else {
			printf("%d.",*p);
			p++;
			s--;
		}
	}
	printf("/ ");
}



static char *tag_names[] = { 
 "BER",          // 0
 "BOOLEAN",        // 1
 "INTEGER",        // 2
 "BIT STRING",       // 3
 "OCTET STRING",      // 4
 "NULL",          // 5
 "OBJECT IDENTIFIER",   // 6
 "ObjectDescriptor",    // 7
 "INSTANCE OF, EXTERNAL", // 8
 "REAL",          // 9
 "ENUMERATED",       // 10
 "EMBEDDED PPV",      // 11
 "UTF8String",       // 12
 "RELATIVE-OID",      // 13
 "undefined(14)",     // 14
 "undefined(15)",     // 15
 "SEQUENCE, SEQUENCE OF", // 16
 "SET, SET OF",      // 17
 "NumericString",     // 18
 "PrintableString",    // 19
 "TeletexString, T61String", // 20
 "VideotexString",     // 21
 "IA5String",       // 22
 "UTCTime",        // 23
 "GeneralizedTime",    // 24
 "GraphicString",     // 25
 "VisibleString, ISO64String", // 26
 "GeneralString",     // 27
 "UniversalString",    // 28
 "CHARACTER STRING",    // 29
 "BMPString"        // 30
};

void asn1show( int depth, struct asn1struct *certificate )
{
  struct asn1struct *token;
  int i; 
 
  token = certificate;
 
  while ( token )
  {
    for ( i = 0; i < depth; i++ )
    {
      printf( " " );
    }
    switch ( token->tag_class )
    {
      case ASN1_CLASS_UNIVERSAL:
        printf( "%s", tag_names[ token->tag ] );
        break;
      case ASN1_CLASS_APPLICATION:
        printf( "application" );
        break;
      case ASN1_CONTEXT_SPECIFIC:
        printf( "context" );
        break;
      case ASN1_PRIVATE:
        printf( "private" );
        break;
    }
    printf( " (%d:%d) ", token->tag, token->length );
  
    if ( token->tag_class == ASN1_CLASS_UNIVERSAL )
    {
      switch ( token->tag )
      {
        case ASN1_INTEGER:
          break;
        case ASN1_BIT_STRING:
      case ASN1_OCTET_STRING:
      {
	      
	      int i;

            for ( i = 0; i < token->length; i++ )
            {
              printf( "%.02x ", token->data[ i ] );
            }
      }
	    break;
      case ASN1_OBJECT_IDENTIFIER:
	      print_oid(token->data, token->length);
#if 0	      
          {
            int i;

            for ( i = 0; i < token->length; i++ )
            {
              printf( "%.02x ", token->data[ i ] );
            }

	    /* print OID in decimalf form */
	    /* first byte is Z=40x+y */
	    {
		    int f,s;
		    const unsigned char * p = token->data;

		    f=*p/40;
		    s=*p%40;
		    printf(" /OID: %d.%d.",f,s);

		    p++;
		    s=token->length-1;

		    while(s>0) {
			    if (*p & 0x80) {
				    unsigned long l=0;
				    int  bcont=1;
				    do {
					    l<<=7;
					    l|= *p & 0x7f;
					    bcont = (*p & 0x80);
					    p++;
					    s--;
				    } while ((s>0) && bcont);
				    // now s==0 of bit7 is 0
					    
				    printf("%ld.",l);
			    } else {
				    printf("%d.",*p);
				    p++;
				    s--;
			    }
		    }
		    printf("/ ");
	    }
          }
#endif	  
          break;
      case ASN1_NUMERIC_STRING:
      case ASN1_PRINTABLE_STRING:
      case ASN1_TELETEX_STRING:
      case ASN1_VIDEOTEX_STRING:
      case ASN1_IA5_STRING:
      case ASN1_UTC_TIME:
      case ASN1_GENERALIZED_TIME:
      case ASN1_GRAPHIC_STRING:
      case ASN1_VISIBLE_STRING:
      case ASN1_GENERAL_STRING:
      case ASN1_UNIVERSAL_STRING:
      case ASN1_CHARACTER_STRING:
      case ASN1_BMP_STRING:
      case ASN1_UTF8_STRING:
      {
        char *str_val = ( char * ) malloc( token->length + 1 );
        strncpy( str_val, ( char * ) token->data, token->length );
         str_val[ token->length ] = 0;
        printf( " %s", str_val ); 
        free( str_val );
      }
        break;
      default:
        break;
    }
  }
  
  printf( "\n" );
  if ( token->children )
  {
    asn1show( depth + 1, token->children );
  }
  token = token->next;
 } 
} 


void asn1free(struct asn1struct *node)
{
#if 1
	struct asn1struct *token;

	if (!node)
		return;
	
	while (node)
	{
		if ( node->children ) {
			asn1free(node->children);
		}
		token=node;
		node = node->next;
		free(node);
	}
#else
	if (!node)
		return;
	asn1free(node->children);
	free(node->children);
	asn1free(node->next);
	free(node->next);
#endif	
} 



int pem_decode( unsigned char *pem_buffer, unsigned char *der_buffer )
{
  unsigned char *pem_buffer_end, *pem_buffer_begin;
  unsigned char *bufptr = der_buffer;
  int buffer_size;
  // Skip first line, which is always "-----BEGIN CERTIFICATE-----".

  if ( strncmp( pem_buffer, "-----BEGIN", 10 ) )
  {
    fprintf( stderr, 
       "This does not appear to be a PEM-encoded certificate file\n" );
    exit( 0 );
  }

  pem_buffer_begin = pem_buffer;
  pem_buffer= pem_buffer_end = strchr( pem_buffer, '\n' ) + 1;

  while ( strncmp( pem_buffer, "-----END", 8 ) )
  {
    // Find end of line
    pem_buffer_end = strchr( pem_buffer, '\n' );
    // Decode one line out of pem_buffer into buffer
    bufptr += base64_decode( pem_buffer, 
      ( pem_buffer_end - pem_buffer ) - 
      ( ( *( pem_buffer_end - 1 ) == '\r' ) ? 1 : 0 ), 
      bufptr );
    pem_buffer = pem_buffer_end + 1;
  } 
 
  buffer_size = bufptr - der_buffer;
 
  return buffer_size;
} 


//----------------------------------------------------------------

//#define TEST_ASN1
#ifdef TEST_ASN1

int main (int argc, char * argv[])
{
  int certificate_file;
  struct stat certificate_file_stat;
  unsigned char * buffer, *bufferptr;
  int buffer_size;
  int bytes_read;

  struct asn1struct certificate;


  if (argc < 2) {
    fprintf(stderr,"Usage: %s <certificate file>\n",argv[0]);
    exit(1);
  }
  if ((certificate_file = open(argv[1], O_RDONLY)) == -1) {
    perror("Unable to open certificate file\n");
    exit(1);
  }

  if (fstat(certificate_file, & certificate_file_stat)) {
    perror("Unable to stat certificate file\n");
    exit(1);
  }
  buffer_size = certificate_file_stat.st_size;
  buffer = (char*) malloc(buffer_size);
  if (!buffer) {
    perror("Not enough memory");
    exit(1);
  }

  bufferptr=buffer;
  while (bytes_read == read(certificate_file, (void*)buffer, certificate_file_stat.st_size)) {
    if (bytes_read<0){
      perror ("read error");
      exit(1);
    }
    bufferptr += bytes_read;
  }

  asn1parse(buffer, buffer_size, &certificate);
  asn1show(1,&certificate);
  asn1free(&certificate);
}

#endif
